using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
using Unity.Burst;
using UnityEngine;
using UnityEngine.Pool;
using Unity.Collections;
using Unity.Mathematics;
using UnityEditor;

namespace Scene.Scripts
{
    /// <summary>
    /// 场景truck
    /// </summary>
    [Serializable]
    public partial class SceneBox
    {
        public enum AssetDetailType //场景内的资源重要级分类
        {
            Important,
            Detail1,
            Detail2,
            Detail3,
            
            DynamicType,  //运动的对象 或者 静态的非Mesh对象
        }

        public class LoadingState
        {
            public const int MaxCount = 8;
            public int LoadingCount { get; private set; } = 0;
            public readonly List<SceneObj> Objs;

            public LoadingState()
            {
                LoadingCount = 0;
                Objs = new List<SceneObj>(300);
            }
            
            public void MarkLoad()
            {
                LoadingCount++;
                
                #if UNITY_EDITOR
                if (LoadingCount > MaxCount)
                {
                    throw new Exception("LoadingState Exception");
                }
                #endif
            }

            public void MarkUnload()
            {
                LoadingCount--;
            }
        }

        /// <summary>
        /// truck交叉点
        /// </summary>
        [Serializable]
        public struct ScenePoint
        {
            public List<int> BoxIds;
            public Bounds PointBound;
            public Vector3 Point => PointBound.center;

            public ScenePoint(Vector3 point)
            {
                this.BoxIds = new List<int>();
                this.PointBound = new Bounds(point, Vector3.one);
            }
        }
        
        public int BoxId;
        public Bounds BoxBounds;
        public readonly Dictionary<AssetDetailType, LoadingState> sceneObjMap;
        
#if UNITY_EDITOR
        [SerializeField]
        private List<string> belongScenes = new List<string>(4);
        [SerializeField]
        public List<string> BeScene => belongScenes;

        public void MarkAtScene(string scene)
        {
            if (this.belongScenes.Contains(scene))
                return;
            this.belongScenes.Add(scene);
        }
#endif

        public bool AnyLoadCount
        {
            get
            {
                if (sceneObjMap == null)
                    return false;
                foreach (var kvp in sceneObjMap)
                {
                    if (kvp.Value.LoadingCount > 0)
                        return true;
                }

                return false;
            }
        }

        public bool GetAnyLoadCount(AssetDetailType detailType)
        {
            {
                if (sceneObjMap == null)
                    return false;
                foreach (var kvp in sceneObjMap)
                {
                    if (kvp.Key == detailType)
                        return kvp.Value.LoadingCount > 0;
                }

                return false;
            }
        }

        public bool TryGetLoadingState(AssetDetailType detailType, out int loadingCount)
        {
            loadingCount = 0;
            if (!sceneObjMap.TryGetValue(detailType, out var loading))
                return false;
            loadingCount = loading.LoadingCount;
            return true;
        }

        public static int GetLoadCount(int boxId,AssetDetailType detailType)
        {
            var box = SceneManager.FindBox(boxId);
            if (box.sceneObjMap == null)
                return 0;
            foreach (var kvp in box.sceneObjMap)
            {
                if (kvp.Key == detailType)
                    return kvp.Value.LoadingCount;
            }

            return 0;
        }

        public SceneBox(int id,Bounds bound)
        {
            this.BoxId = id;
            this.BoxBounds = bound;
            this.sceneObjMap = new Dictionary<AssetDetailType, LoadingState>(100);
            var v = System.Enum.GetValues(typeof(AssetDetailType));
            foreach (var item in v)
            {
                this.sceneObjMap.Add((AssetDetailType)item,new LoadingState());
            }
        }

        public void OnLoad(AssetDetailType detailType)
        {
            if (!sceneObjMap.TryGetValue(detailType, out var loading))
                return;
            loading.MarkLoad();
            CurrentMaxLoadTimes = Mathf.Max(CurrentMaxLoadTimes, loading.LoadingCount);

            if (!SceneManager.ReadyLoaded.TryGetValue(detailType, out var linkList))
            {
                linkList = new System.Collections.Generic.LinkedList<int>();
                SceneManager.ReadyLoaded.Add(detailType, linkList);
            }

            if (linkList.Count == 0)
            {
                linkList.AddLast(RuntimeData.AcquireNode(BoxId));
            }
            else
            {
                var x = SceneManager.FindTarget(detailType, x => x == BoxId);
                if (x != null)
                {
                    SceneManager.RefreshList(detailType, x, true);
                }
                else
                {
                    linkList.AddLast(RuntimeData.AcquireNode(BoxId));
                }
            }
        }

        public void OnUnload(AssetDetailType detailType)
        {
            if (!sceneObjMap.TryGetValue(detailType, out var loading))
                return;
            loading.MarkUnload();
        }
    }

    [Serializable]
    public struct LoadRadius
    {
        public SceneBox.AssetDetailType detailType;
        public float radius;
    }

    /// <summary>
    /// 场景物件
    /// </summary>
    [System.Serializable]
    public abstract class SceneObj : SceneObj.IObj
    {
        public interface IObj 
        {
            string Name { get; }
            void OnLod(int lastLod, int lod);
            void OnUnload();
            
        }
        public interface IDynamicObj
        {
            Transform DynamicTransform { get; }
            // IObj GetObj { get; }
            bool IsStatic { get; }
        }

        public abstract class BaseDynamicObjAgent : MonoBehaviour, IObj
        {
            [SerializeField] 
            private bool _defaultEnableState;
            public string Name => gameObject.name;

            public virtual bool IsStatic => true; 

            public void OnLod(int lastLod, int lod)
            {
                enabled = _defaultEnableState;

                OnSceneObjLod(lastLod, lod);
            }

            public void OnUnload()
            {
                enabled = false;

                OnSceneUnload();
            }

            public void SetDefault()
            {
                _defaultEnableState = enabled;
                enabled = false;
                OnDefaultSet();
            }

            protected virtual void OnDefaultSet(){}
            protected virtual void OnSceneObjLod(int lastLod,int lod){}
            protected virtual void OnSceneUnload(){}
        }
        
        private bool _loadingFlag;
        private int _loadCount;
        [SerializeField]
        private SceneBox.AssetDetailType _detailType;
        [SerializeField]
        public Bounds objBounds;
        public SceneBox.AssetDetailType DetailType => _detailType;
        public abstract string Name { get; }
        public abstract void OnLod(int lastLod, int lod);

        public abstract void OnUnload();
        public abstract bool IsReleased { get; }
        public abstract bool LodAble { get; }

        protected SceneObj(Bounds aabb,SceneBox.AssetDetailType detailType)
        {
            _loadCount = 0;
            _detailType = detailType;
            _loadingFlag = false;
            objBounds = aabb;
        }

        public abstract bool TryGetLodBehaviours(out List<BaseLodBehaviour> behaviours);

        public static void MarkReadyLoad(SceneBox fromBox, SceneObj obj, SceneBox.AssetDetailType detailType)
        {
            if (obj._loadingFlag)
            {
                return;
            }

            if (!RuntimeData.ReadyLoadedMap.TryGetValue(detailType, out var que))
            {
                que = new Queue<KeyValuePair<SceneBox, SceneObj>>(300);
                RuntimeData.ReadyLoadedMap.Add(detailType, que);
            }
            que.Enqueue(new KeyValuePair<SceneBox, SceneObj>(fromBox,obj));
        }

        public static void LoadFromScene(bool loadRefresh)
        {
            UnloadCheck();
            LoadCheck();
            RuntimeData.LoadCountEveryFrame = RuntimeData.UnloadCountEveryFrame = 0;
            OnLod();
        }

        private static void LoadCheck()
        {
            var max = SceneBox.CurrentMaxLoadTimes;
            for (int i = max; i > 0; i--)
            {
                if (RuntimeData.LoadCountEveryFrame > SceneBox.MaxLoadEveryFrame)
                    continue;
                
                foreach (var kvp in RuntimeData.ReadyLoadedMap)
                {
                    var que = kvp.Value;
                    var count = que.Count;
                    while (count > 0)
                    {
                        count--;
                        var item = que.Dequeue();
                        if (!item.Key.TryGetLoadingState(kvp.Key, out var loadingCount))
                        {
                            continue;
                        }

                        if (loadingCount <= 0)
                        {
                            continue;
                        }

                        if (loadingCount >= i)
                        {
                            if (item.Value.MarkAssetLoadState())
                            {
                                if (SceneUtility.SceneObjCheckRemove(item.Key,kvp.Key,item.Value))
                                {
                                    continue;
                                }

                                var t = new ObjLoaded(item.Key, item.Value, kvp.Key);
                                RuntimeData.LoadedLinkList.AddLast(RuntimeData.AcquireNode(t));
                            }
                        }
                        else
                        {
                            RuntimeData.mCacheStack.Push(item);
                        }
                    }

                    while (RuntimeData.mCacheStack.Count != 0)
                    {
                        var item = RuntimeData.mCacheStack.Pop();
                        que.Enqueue(item);
                    }
                }
            }
        }
        private static void UnloadCheck()
        {
            if (RuntimeData.searchPtr == null)
                RuntimeData.searchPtr = RuntimeData.LoadedLinkList.First;
            
            while (RuntimeData.searchPtr != null)
            {
                var v = RuntimeData.searchPtr.Value;
                if (!v.FromBox.TryGetLoadingState(v.DetailType, out var loadingCount))
                {
                    RuntimeData.searchPtr = RuntimeData.searchPtr.Next;
                    continue;
                }
                if (loadingCount <= 0)
                {
                    bool released = SceneUtility.SceneObjCheckRemove(v.FromBox, v.DetailType, v.Obj);
                    if (released || v.Obj.MarkAssetUnLoadState())
                    {
                        if (released == false)
                        {
                            v.Unload();
                        }

                        var p = RuntimeData.searchPtr.Previous;
                        RuntimeData.LoadedLinkList.Remove(RuntimeData.searchPtr);
                        RuntimeData.ReleaseNode(RuntimeData.searchPtr);
                        RuntimeData.searchPtr = p;
                    }
                }

                if (RuntimeData.searchPtr == null)
                    break;
                RuntimeData.searchPtr = RuntimeData.searchPtr.Next;
            }
        }

        private static void OnLod()
        {
            if (RuntimeData.LoadedLinkList.Count == 0)
                return;
            var p = RuntimeData.LoadedLinkList.First;
            while (p != null)
            {
                if (p.Value.Obj.IsReleased == false)
                {
                    if (p.Value.Obj.LodAble)  //适用情形：物体处于室内时，会跳过Lod更新，加载依赖于lod更新，所以会跳过加载
                    {
                        var lod = SceneBox.SceneViewCenter.View.TestLodLevel(p.Value.DetailType, p.Value.Obj);
                        if (lod != p.Value.Lod)
                        {
                            var tmp = p.Value;
                            tmp.UpdateLod(lod);
                            p.Value = tmp;   //p.value为结构体
                        }
                    }
                    else
                    {
                        p.Value.UpdateLod(-1);
                    }
                }

                p = p.Next;
            }
        }
        
        private bool MarkAssetLoadState()
        {
            _loadCount++;
            _loadingFlag = true;

            return _loadCount == 1;
        }
        
        private bool MarkAssetUnLoadState()
        {
            --_loadCount;
            if (_loadCount <= 0)
            {
                _loadingFlag = false;
                return true;
            }

            return false;
        }
        public static void ReleaseRuntime()
        {
            RuntimeData.Release();
        }
    }
    public struct ObjLoaded
    {
        public int Lod { get; private set; }

        public readonly SceneObj Obj;
        public readonly SceneBox FromBox;
        public readonly SceneBox.AssetDetailType DetailType;
            
        public ObjLoaded(SceneBox box, SceneObj obj, SceneBox.AssetDetailType detailType)
        {
            this.Obj = obj;
            this.FromBox = box;
            this.DetailType = detailType;
            this.Lod = -1;
        }

        public void UpdateLod(int lod)
        {
            var lastLod = Lod;
            this.Lod = lod;

            Obj.OnLod(lastLod,lod);
            
            if (Obj.TryGetLodBehaviours(out var list))
            {
                foreach (var item in list)
                {
                    item.OnLod(lastLod, lod);
                }
            }
        }

        public void Unload()
        {
            if (Obj.TryGetLodBehaviours(out var list))
            {
                foreach (var item in list)
                {
                    item.OnUnload();
                }
            }
            
            //lod为-1时某些情况也需要卸载
            // if (Lod == -1)
            //     return;
            Lod = -1;
            Obj.OnUnload();
        }
    }

    class RuntimeData
    {
        private static List<LinkedListNode<ObjLoaded>> nodePool = new List<LinkedListNode<ObjLoaded>>(500);
        private static List<LinkedListNode<int>> intNodePool = new List<LinkedListNode<int>>(300);
        public static LinkedListNode<ObjLoaded> searchPtr = null;
        public static readonly LinkedList<ObjLoaded> LoadedLinkList = new LinkedList<ObjLoaded>();

        public static readonly Dictionary<SceneBox.AssetDetailType, Queue<KeyValuePair<SceneBox, SceneObj>>>
            ReadyLoadedMap = new Dictionary<SceneBox.AssetDetailType, Queue<KeyValuePair<SceneBox, SceneObj>>>(5);

        public static readonly Stack<KeyValuePair<SceneBox, SceneObj>> mCacheStack =
            new Stack<KeyValuePair<SceneBox, SceneObj>>(100);

        public static Dictionary<int, MeshMatData.MeshMat> allLoadedMesh =
            new Dictionary<int, MeshMatData.MeshMat>(3000);

        public static int LoadCountEveryFrame { get; set; } = 0;
        public static int UnloadCountEveryFrame { get; set; } = 0;

        public static LinkedListNode<ObjLoaded> AcquireNode(ObjLoaded t)
        {
            var n = nodePool.Count;
            if (n == 0)
            {
                return new LinkedListNode<ObjLoaded>(t);
            }

            var tmp = nodePool[n - 1];
            nodePool.RemoveAt(n - 1);
            tmp.Value = t;
            return tmp;
        }
        public static void ReleaseNode(LinkedListNode<ObjLoaded> t)
        {
            t.Value = default;
            var n = nodePool.Count;
            if (n > 500)
            {
                return;
            }

            nodePool.Add(t);
        }
        
        public static LinkedListNode<int> AcquireNode(int t)
        {
            var n = intNodePool.Count;
            if (n == 0)
            {
                return new LinkedListNode<int>(t);
            }
            
            var tmp = intNodePool[n - 1];
            intNodePool.RemoveAt(n - 1);
            tmp.Value = t;
            return tmp;
        }
        public static void ReleaseNode(LinkedListNode<int> t)
        {
            t.Value = 0;
            var n = intNodePool.Count;
            if (n > 300)
            {
                return;
            }
            intNodePool.Add(t);
        }

        public static void Release()
        {
            LoadedLinkList.Clear();
            ReadyLoadedMap.Clear();
            mCacheStack.Clear();
            allLoadedMesh.Clear();
            LoadCountEveryFrame = 0;
            UnloadCountEveryFrame = 0;
            nodePool.Clear();
            intNodePool.Clear();
        }
    }

    [Serializable]
    public class MeshMatData : SceneObj
    {
        [SerializeField] 
        private string meshAssetId;
        [SerializeField]
        public Transform objTransform;
        [SerializeField]
        public MeshFilter objMesh;
        [SerializeField]
        public MeshRenderer objRender;

        public bool DontLoadFlag { get; set; } = false;  //标识物理需不需要被加载

        public override string Name => objTransform.name;

        public MeshMatData(Bounds aabb, SceneBox.AssetDetailType detailType, string assetId, Transform t, MeshFilter filter, MeshRenderer renderer) : base(aabb,detailType)
        {
            meshAssetId = assetId;
            objTransform = t;
            objMesh = filter;
            objRender = renderer;
        }

        public override void OnLod(int lastLod, int lod)
        {
            if (lod != -1) //加载
            {
                LoadAsset(this, lod);
            }
            
            if (lod == -1)
            {
                objMesh.sharedMesh = null;
                objRender.sharedMaterial = null;
            }
        }

        public override void OnUnload()
        {
            UnLoadAsset(this);
        }

        public override bool IsReleased => objTransform == null;
        public override bool LodAble => DontLoadFlag == false;
        public override bool TryGetLodBehaviours(out List<BaseLodBehaviour> behaviours)
        {
            behaviours = _list;
            if (_list != null)
            {
                return _list.Count > 0;
            }
            
            var items = objTransform.GetComponents<BaseLodBehaviour>();
            if (items != null && items.Length != 0)
            {
                _list = new List<BaseLodBehaviour>(items.Length);
                _list.AddRange(items);
                return true;
            }
            return false;
        }

        private List<BaseLodBehaviour> _list = null;

        private static void LoadAsset(MeshMatData loadObj,int lod)
        {
            var hash = loadObj.GetHashCode();
            
            if (!RuntimeData.allLoadedMesh.TryGetValue(hash, out var meshMat) || meshMat.HasLod(lod) == false)
            {
                if (ReferenceEquals(meshMat,null))
                {
                    meshMat = new MeshMat();
                    RuntimeData.allLoadedMesh.Add(hash,meshMat);   
                }
                
                RuntimeData.LoadCountEveryFrame++;
                SceneAssetPool.LoadAssetAsync(loadObj.meshAssetId, (mesh, mat) =>
                {
                    meshMat.SetMeshMat(lod,mesh,mat);
                    loadObj.objMesh.sharedMesh = mesh;
                    loadObj.objRender.sharedMaterial = mat;
                    if (mesh != null) meshMat.RefCount++;
                    if (mat != null) meshMat.RefCount++;
                    RuntimeData.allLoadedMesh[hash] = meshMat;
                });
                return;
            }

            var mm = meshMat.GetMeshMat(lod);
            loadObj.objMesh.sharedMesh = mm.Item1;
            loadObj.objRender.sharedMaterial = mm.Item2;
            if (mm.Item1 != null) meshMat.RefCount++;
            if (mm.Item2 != null) meshMat.RefCount++;
        }
         private static void UnLoadAsset(MeshMatData loadObj)
         {
             var hash = loadObj.GetHashCode();

             if (!RuntimeData.allLoadedMesh.TryGetValue(hash, out var meshMat))
             {
                 return;
             }
            
             for (int i = 0; i < 2; i++)
             {
                 var tmp = meshMat.GetMeshMat(i);
                 loadObj.objMesh.sharedMesh = null;
                 loadObj.objRender.sharedMaterial = null;
                 if (tmp.Item1 != null) meshMat.RefCount--;
                 if (tmp.Item2 != null) meshMat.RefCount--;
             }
         }

        private static void OnAssetReleaseCheck(MeshMat meshMat)
        {
            Debug.Assert(meshMat.RefCount == 0,"OnAssetReleaseCheck UnKnow error");
            for (int i = 0; i < 2; i++)
            {
                var tmp = meshMat.GetMeshMat(i);
                if (tmp.Item1 != null)
                {
                    SceneAssetPool.Unload(tmp.Item1);
                    SceneUtility.LogInfo($"资源被卸载 {tmp.Item1.name}");
                }

                if (tmp.Item2 != null)
                {
                    SceneAssetPool.Unload(tmp.Item2);
                    SceneUtility.LogInfo($"资源被卸载 {tmp.Item2.name}");
                }
            }
        }

        public class MeshMat
        {
            private Mesh mesh1;
            private Mesh mesh2;
            private Mesh mesh3;
            private Material mat1;
            private Material mat2;
            private Material mat3;
            private int _loadRefCount = 0;

            public int RefCount
            {
                get => _loadRefCount;
                set
                {
                    _loadRefCount = value;
                    if (_loadRefCount == 0)
                    {
                        OnAssetReleaseCheck(this);
                    }
                }
            }

            public (Mesh, Material) GetMeshMat(int lod)
            {
                if (lod < 0 || lod > 2)
                    throw new System.Exception("Get mesh mat error");
                if (lod == 0)
                    return (mesh1, mat1);
                if (lod == 1)
                    return (mesh2, mat2);
                return (mesh3, mat3);
            }

            public bool HasLod(int lod)
            {
                var mm = GetMeshMat(lod);
                return mm.Item1 != null || mm.Item2 != null;
            }

            public void SetMeshMat(int lod, Mesh mesh, Material mat)
            {
                if (lod < 0 || lod > 2)
                    throw new System.Exception("Get mesh mat error");
                if (lod == 0)
                {
                    mesh1 = mesh;
                    mat1 = mat;
                    return;
                }

                if (lod == 1)
                {
                    mesh2 = mesh;
                    mat2 = mat;
                    return;
                }

                mesh3 = mesh;
                mat3 = mat;
            }
        }
    }

    [Serializable]
    public class ZoomData : MeshMatData
    {
        [SerializeField]
        private List<MeshMatData> _zoomList;
        public GameObject roomCheck;

        public ZoomData(List<MeshMatData> zoomInList, Bounds aabb, SceneBox.AssetDetailType detailType, string assetId, Transform t, MeshFilter filter, MeshRenderer renderer) : base(aabb, detailType, assetId, t, filter, renderer)
        {
            _zoomList = zoomInList;
            UnLoadZoom();
        }

        public void LoadZoom()
        {
            foreach (var item in _zoomList)
            {
                item.DontLoadFlag = false;
            }
        }

        public void UnLoadZoom()
        {
            foreach (var item in _zoomList)
            {
                item.DontLoadFlag = true;
            }
        }
    }
    
    [Serializable]
    public class DynamicObjData : SceneObj,SceneObj.IDynamicObj
    {
        public GameObject mAgent;

        public DynamicObjData(Bounds aabb, SceneBox.AssetDetailType detailType, GameObject notifyOBj) : base(aabb,
            detailType)
        {
            mAgent = notifyOBj.gameObject;
        }

        public override string Name => mAgent.name;
        public override void OnLod(int lastLod, int lod)
        {
        }

        public override void OnUnload()
        {
        }

        public Transform DynamicTransform => mAgent.transform;

        public bool IsStatic
        {
            get
            {
                var dy = mAgent.GetComponent<BaseDynamicObjAgent>();
                return dy == null || dy.IsStatic;
            }
        }

        public override bool IsReleased => mAgent == null;
        public override bool LodAble => true;
        public override bool TryGetLodBehaviours(out List<BaseLodBehaviour> behaviours)
        {          
            behaviours = _list;
            if (_list != null)
            {
                return _list.Count > 0;
            }
            
            var items = mAgent.GetComponents<BaseLodBehaviour>();
            if (items != null && items.Length != 0)
            {
                _list = new List<BaseLodBehaviour>(items.Length);
                _list.AddRange(items);
                return true;
            }
            return false;
        }

        private List<BaseLodBehaviour> _list = null;
    }

    public abstract class BaseLodBehaviour : MonoBehaviour
    {
        private bool _defaultEnableState = false;
        public virtual void OnLod(int lastLod, int lod)
        {
            
        }
        public virtual void OnUnload()
        {
            
        }
    }

    public sealed class ZoomLodBehaviour : BaseLodBehaviour
    {
        private bool _zoomEnterFlag;
        private bool _zoomExitFlag;
        private List<MeshMatData> _datas;
        public override void OnLod(int lastLod, int lod)
        {

        }

        public override void OnUnload()
        {
        }
        
        private void LoadZoomAssets()
        {}
        private void UnloadZoomAssets()
        {}

        private void OnTriggerEnter(Collider other)
        {
            if (other.tag.Equals("SK_ZoomEnter"))
            {
                _zoomExitFlag = false;
                _zoomEnterFlag = true;
                
                //加载室内信号，开始把室内物理标记位可加载状态
            }
            if(other.tag.Equals("SK_ZoomExit"))
            {
                _zoomEnterFlag = false;
                _zoomExitFlag = true;
                
                //
            }
        }
    }
}